#!/usr/bin/env python
import os
import sys
import p4lib
import ConfigParser
import socket
import marshal
from optparse import OptionParser
from collections import namedtuple

def error(info):
    print >> sys.stderr , info
    sys.exit(0)

def warn(info):
    print >> sys.stderr , info
    
def is_exe(fpath):
    return os.path.isfile(fpath) and os.access(fpath, os.X_OK)

def find_on_path(fname):
    for path in os.environ["PATH"].split(os.pathsep):
        path = path.strip('"').strip("'")
        exe_file = os.path.join(path, fname)
        if is_exe(exe_file):
            return exe_file
    return None

def which(program):
    if is_exe(program): return os.path.abspath(program)
    else:
        return find_on_path(program) 
class progressBar:
	def __init__(self, minValue = 0, maxValue = 10, totalWidth=12):
		self.progBar = "[]"   # This holds the progress bar string
		self.min = minValue
		self.max = maxValue
		self.span = maxValue - minValue
		self.width = totalWidth
		self.amount = 0       # When amount == max, we are 100% done 
		self.updateAmount(0)  # Build progress bar string

	def updateAmount(self, newAmount = 0):
		if newAmount < self.min: newAmount = self.min
		if newAmount > self.max: newAmount = self.max
		self.amount = newAmount

		# Figure out the new percent done, round to an integer
		diffFromMin = float(self.amount - self.min)
		percentDone = (diffFromMin / float(self.span)) * 100.0

		# Figure out how many hash bars the percentage should be
		allFull = self.width - 2
		numHashes = (percentDone / 100.0) * allFull
		numHashes = int(round(numHashes))

		# build a progress bar with hashes and spaces
		self.progBar = "[" + '#'*numHashes + ' '*(allFull-numHashes) + "]"

		percentString = " %.2f" % percentDone + "% Done "
		# figure out where to put the percentage, roughly centered
		percentPlace = (len(self.progBar) / 2) - len(percentString) 


		# slice the percentage into the bar
		self.progBar = self.progBar[0:percentPlace] + percentString + self.progBar[percentPlace+len(percentString):]

	def __str__(self):
		return str(self.progBar)

class cfg(object):
    """A config file implemtation"""
    LOC_SEC = 'locations'
    MOD_CEC = 'modules'
    location = namedtuple('location', ['port','path'])
    module = namedtuple('module', ['name','path','location', 'revision', 'platform'])
    def __init__(self, p4,path=None ,**options):
        self.p4 = p4
        self.path = path
        self.check()
        config = ConfigParser.ConfigParser()
        try:
            config.readfp(open(self.path))
        except ConfigParser.Error:
            error("%s parsing error." % self.path)
        secs = config.sections()
        if not set([cfg.LOC_SEC,cfg.MOD_CEC]) <= set(secs):
            error('%s or %s sectionis missing' % LOCSEC,MODSEC);
        self.locations = {}
        for item in config.items(cfg.LOC_SEC):
            item[1] in secs or error("%s is missing." % item[1])
            tdict = dict(config.items(item[1]))
            loc = cfg.location(**tdict)
            self.locations[item[0]] = loc
        self.modules = {}
        for item in config.items(cfg.MOD_CEC):
            item[1] in secs or error("%s is missing." % item[1])
            tdict = dict(config.items(item[1]))
            mod = cfg.module(**tdict)
            self.modules[item[0]] = mod
    def get_mod(self, mod):
        return self.modules[mod] if mod in self.modules else None
    def get_loc(self, loc):
        return self.locations[loc] if loc in self.locations else None
    def get_modules(self):
        return self.modules.values()
    def get_locations(self):
        return self.locations.values()
    def check(self):
        os.path.exists(self.path) or error('%s does not exist.' % self.path)
    
class wkspc(object):
    def __init__(self, root, module, location):
        self.root = os.path.abspath(os.path.join(root,module.path))
        self.server = location.port
        self.spath = location.path
        self.srev = module.revision
        self.name = self.gen_clientname()
        p4 = p4lib.P4(p4=p4exec,port=self.server,user=p4user)
        ret = p4._p4run(("sizes -s %s%s" % (self.spath,self.srev)).split() ,**{'-G':''} ) [0]
        info = marshal.loads(ret)
        if 'errors' in info: error("%s%s is not valid depod path." %(self.spath,self.srev))
        self.fileCount = int(info['fileCount'])
        self.fileSize = int(info['fileSize'])
        self.spec = {
             'client': self.name,
             'description': 'Created by pw script.',
             'host': socket.gethostname(),
             'lineend': 'local',
             'options': 'noallwrite noclobber compress unlocked nomodtime normdir',
             'owner': p4user,
             'root': self.root,
             'view': '%s //%s/...' % (location.path, self.name)}
        p4.client(client=self.spec)
        self.p4 = p4lib.P4(p4=p4exec,port=self.server,user=p4user,client=self.name)
        pass
    def gen_clientname(self):
        return 'pw_%s' % socket.gethostname()
    def sync(self):
        print 'Fetching %s[%s%s] => %s' % (self.server,self.spath,self.srev,self.root)
        p4lib._hook_fun = self.hook
        results = self.p4.sync(["%s%s" % (self.spath,self.srev)],dryrun=dry,keep=0,_raw=1)
        p4lib._hook_fun = None
    def hook(self,i,o,e):
        prog = progressBar(0,self.fileCount,70)
        p = 0;
        for line in o:
            p += 1
            prog.updateAmount(p)
            print str(prog),'\r',
        print ''
        return i,o,e

def p4exec_func(option, opt, value, parser):
    if opt == '--perforce' or opt == '-p'and value:
        p4exec = which(value) or  warn(value + ' not found')

__CSL = None
def symlink(source, link_name):
    '''symlink(source, link_name)
       Creates a symbolic link pointing to source named link_name'''
    global __CSL
    if __CSL is None:
        import ctypes
        csl = ctypes.windll.kernel32.CreateSymbolicLinkW
        csl.argtypes = (ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_uint32)
        csl.restype = ctypes.c_ubyte
        __CSL = csl
    flags = 0
    if source is not None and os.path.isdir(source):
        flags = 1
    if __CSL(link_name, source, flags) == 0:
        raise ctypes.WinError()

def mklink(link, target):
    print 'Link %s => %s' % (link,target)
    if dry: return
    link = os.path.abspath(link)
    pdir = os.path.dirname(link)
    if os.path.isfile(pdir): error ("%s is not a folder." % pdir)
    if not os.path.isdir(pdir):
        os.makedirs(pdir)
    if hasattr(os,'symlink'):
        os.symlink(target,link)
    else:
        os.symlink = symlink
        os.symlink(target,link)

def pw(argv):
    parser = OptionParser("Usage: %prog [options] <workspace> <config file>")
    parser.add_option("-p", "--p4exec", action="store", type="string", default= 'p4.exe' if sys.platform == 'win32' else 'p4', metavar='path', help='p4 executable path, default is p4')
    parser.add_option("-s", "--server", action="store", type="string", default= 'perforce:1666', metavar='perforce:1666',help='perforce server address and port, default is perforce:1666')
    parser.add_option("-u", "--user"  , action="store", type="string", default= None, metavar='<user name>',help='perfrce user name, default is $P4USER')
    parser.add_option("-P", "--pass"  , action="store", type="string", default= None, metavar='<password>',help='perforce password')  
    parser.add_option("-d", "--dry"   , action="store_true",default= False, metavar='',help='dry run')
    parser.add_option("-l", "--local", action="store", type="string", default= None, metavar='path', help='local storage path if any')
   
    options, args = parser.parse_args(argv)
    if not args or len(args) != 2:
        parser.print_help()
        sys.exit(1)
    
    global p4exec,p4client,p4user, dry, store
    dry = options.dry
    store = options.local
    if store: store = os.path.abspath(store)
    if store: os.path.isdir(store) or error ('%s does not exist.' % store)
    p4exec = which(options.p4exec) or error(options.p4exec + ' not found.')

    p4 = p4lib.P4(p4=p4exec,port=options.server)
    
    ret = p4._p4run('set P4USER'.split())
    if ret:
        p4user = ret[0].split('=')[1].split()[0]
    
    clients = p4.clients(_user=p4user);

    clients_name = (x['client'] for x in clients)
    if not args[0] in clients_name: error('workspace %s not found.' % args[0])
    
    p4client = args[0]
    
    spec = p4.client(name=p4client)
    
    p4 = p4lib.P4(p4=p4exec,port=options.server,user=p4user,client=args[0])
    #ret = p4.fstat('//%s/subdoc/...' % p4client)
    mycfg = cfg(p4,args[1])
    
    for mod in mycfg.get_modules():
        if store:
            lpath = os.path.abspath(os.path.join(spec['root'], mod.path))
            tpath = os.path.abspath(os.path.join(store,mod.path))
            if os.path.exists(lpath) : error( '%s already exists.' % lpath )
            if not os.path.exists(tpath):
                wk = wkspc(store, mod,mycfg.get_loc(mod.location))
                wk.sync()
            else:
                print "%s already exists. population is skipped." % tpath
            mklink(lpath, tpath )
        else:
            wk = wkspc(spec['root'], mod,mycfg.get_loc(mod.location))
            wk.sync()
    pass

if __name__ == '__main__':
    sys.exit( pw(sys.argv[1:]))
